/*
 * (C) Copyright 2007-2015
 * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
 * mmc_bsp.c
 * Description: MMC driver for General mmc operations
 * Author: WenJiaqiang
 * Date: 2016/6/26 19:52:12
 */

#include "common.h"
#include "mmc_def.h"
#include "mmc_bsp.h"
#include "mmc_host_2_v5p1.h"
#include "mmc.h"
#include <asm/arch/gpio.h>
#include <private_boot0.h>
#include <private_toc.h>
#include <private_uboot.h>

extern int mmc_config_addr; /* const boot0_file_head_t BT0_head; */
extern int mmc_register(int dev_num, struct mmc *mmc);
extern int mmc_unregister(int dev_num);

extern struct mmc mmc_dev[MAX_MMC_NUM];
extern struct sunxi_mmc_host mmc_host[MAX_MMC_NUM];

static struct mmc_reg_v5p1 reg_v5p1_bak;

static void mmc_v5p1_save_regs(struct mmc *mmc, void *p)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v5p1* reg = (struct mmc_reg_v5p1*)mmchost->reg;
    struct mmc_reg_v5p1* reg_bak = (struct mmc_reg_v5p1 *)p;

    memset(p, 0x0, sizeof(struct mmc_reg_v5p1));

    reg_bak->ctrl1          = reg->ctrl1          ;
    reg_bak->rst_clk_ctrl   = reg->rst_clk_ctrl   ;
    reg_bak->int_sta_en     = reg->int_sta_en     ;
    reg_bak->int_sig_en     = reg->int_sig_en     ;
    reg_bak->acmd_err_ctrl2 = reg->acmd_err_ctrl2 ;
    reg_bak->ctrl3          = reg->ctrl3          ;
    reg_bak->to_ctrl        = reg->to_ctrl        ;
    reg_bak->thld           = reg->thld           ;
    reg_bak->atc            = reg->atc            ;
    reg_bak->rtc            = reg->rtc            ;
    reg_bak->ditc0          = reg->ditc0          ;
    reg_bak->ditc1          = reg->ditc1          ;
    reg_bak->tp0            = reg->tp0            ;
    reg_bak->tp1            = reg->tp1            ;
    reg_bak->ds_dly         = reg->ds_dly         ;

}

static void mmc_v5p1_restore_regs(struct mmc *mmc, void *p)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v5p1* reg = mmchost->reg;
    struct mmc_reg_v5p1* reg_bak = (struct mmc_reg_v5p1 *)p;

    reg->ctrl1          = reg_bak->ctrl1          ;
    reg->rst_clk_ctrl   = reg_bak->rst_clk_ctrl   ;
    reg->int_sta_en     = reg_bak->int_sta_en     ;
    reg->int_sig_en     = reg_bak->int_sig_en     ;
    reg->acmd_err_ctrl2 = reg_bak->acmd_err_ctrl2 ;
    reg->ctrl3          = reg_bak->ctrl3          ;
    reg->to_ctrl        = reg_bak->to_ctrl        ;
    reg->thld           = reg_bak->thld           ;
    reg->atc            = reg_bak->atc            ;
    reg->rtc            = reg_bak->rtc            ;
    reg->ditc0          = reg_bak->ditc0          ;
    reg->ditc1          = reg_bak->ditc1          ;
    reg->tp0            = reg_bak->tp0            ;
    reg->tp1            = reg_bak->tp1            ;
    reg->ds_dly         = reg_bak->ds_dly         ;

}

void mmc_v5p1_module_reset(struct mmc *mmc)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    u32 timeout = 0xff;

    mmchost->hclkbase &= (~(1U<<10)); //only for smch2
    mmchost->hclkrst  &= (~(1U<<10));

    timeout = 0xff;
    while (--timeout>0);

    mmchost->hclkbase |= (1U<<10); //only for smch2
    mmchost->hclkrst  |= (1U<<10);
}



static int mmc_core_init(struct mmc *mmc)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v5p1 *reg = (struct mmc_reg_v5p1 *)mmchost->reg;
    u32 rval = 0;

    writel(0xffffffff,&reg->int_sta);    /* clear all interrupt         */
    writel(0xffffffff,&reg->int_sta_en); /* enable all interrupt status */
    writel(0x00000000,&reg->int_sig_en);
    writel(0xffffffff,&reg->to_ctrl);    /* configure timeout           */

    /* enable write/read stop clock at block gap, bit8 read, bit9 write */
    rval  = readl(&reg->ctrl3);
    rval |= (0x3<<8);
    writel(rval,&reg->ctrl3);

    return 0;
}

static int mmc_config_clock(struct mmc *mmc, unsigned clk)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host* )mmc->priv;
    struct mmc_reg_v5p1 *reg = (struct mmc_reg_v5p1 *)mmchost->reg;
    u32 rval = 0;

    if ((mmc->speed_mode == HSDDR52_DDR50) || (mmc->speed_mode == HS400)) {
        if (clk > mmc->f_max_ddr)
            clk = mmc->f_max_ddr;
    }

    /* disable card clock */
    rval = readl(&reg->rst_clk_ctrl);
    rval &= ~(1U << 2);
    writel(rval,&reg->rst_clk_ctrl);

    /* configure clock */
    if (mmc->speed_mode == HSDDR52_DDR50)
        mmchost->mod_clk = clk <<3;
    else
        mmchost->mod_clk = clk <<2;

    mmc_set_mclk(mmchost, mmchost->mod_clk);

    /* get mclk */
    if (mmc->speed_mode == HSDDR52_DDR50)
        mmc->clock = mmc_get_mclk(mmchost) >>3;
    else
        mmc->clock = mmc_get_mclk(mmchost) >>2;

    mmchost->clock = mmc->clock; /* bankup current clock frequency at host */
    mmcdbg("get round card clk %d, mod_clk %d\n", mmc->clock, mmchost->mod_clk);

    /* re-enable mclk */
    writel(readl(mmchost->mclkbase)|(1<<31), mmchost->mclkbase);
    mmcdbg("mmc %d mclkbase 0x%x\n", mmchost->mmc_no, readl(mmchost->mclkbase));


    /* Re-enable card clock */
    rval = readl(&reg->rst_clk_ctrl);
    rval |= (1U << 2);
    writel(rval,&reg->rst_clk_ctrl);

    /* configure delay for current frequency and speed mode */
    /*
        TODO...
    */

    return 0;
}

static void mmc_set_ddr_mode(struct mmc *mmc)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v5p1 *reg = (struct mmc_reg_v5p1 *)mmchost->reg;
    u32 rval = reg->acmd_err_ctrl2;

    rval &= ~(0x7U<<16);
    if (mmc->speed_mode == HSDDR52_DDR50) {
        writel(0x50200000,&reg->atc);
        rval |= (0x4U<<16);
    } else if (mmc->speed_mode == HS400) {
        writel(0x50200000,&reg->atc);
        rval |= (0x5U<<16);
    } else {
        writel(0x30200000,&reg->atc);
    }
    writel(rval,&reg->acmd_err_ctrl2);
}

static void mmc_set_ios(struct mmc *mmc)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v5p1 *reg = (struct mmc_reg_v5p1 *)mmchost->reg;
    u32 rval = 0;

    mmcdbg("mmc %d ios: bus: %d, clock: %d\n", mmchost ->mmc_no,
        mmc->bus_width, mmc->clock);

    if (mmc->clock && mmc_config_clock(mmc, mmc->clock)) {
        mmcinfo("[mmc]: " "*** update clock failed\n");
        mmchost->fatal_err = 1;
        return;
    }
    /* change bus width */
    rval = readl(&reg->ctrl1);

    if (mmc->bus_width == 8) {
        rval &= ~(0x1U<<1);
        rval |= 0x1U<<5;
    } else if (mmc->bus_width == 4) {
        rval |= 0x1U<<1;
        rval &= ~(0x1<<5);
    } else {
        rval &= ~(0x1U<<1);
        rval &= ~(0x1U<<5);
    }
    writel(rval,&reg->ctrl1);

    /* set ddr mode */
    mmc_set_ddr_mode(mmc);
}

static int mmc_trans_data_by_cpu(struct mmc *mmc, struct mmc_data *data)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v5p1 *reg = (struct mmc_reg_v5p1 *)mmchost->reg;
    unsigned i;
    unsigned byte_cnt = data->blocksize * data->blocks;
    unsigned *buff;
    unsigned timeout = 0xffffff;
    unsigned fifo_level = 0, fifo_level_leftover = 0;

    /* use ahb to access fifo */
    reg->ctrl3 |= CPUAcessBuffEn;

    if (data->flags & MMC_DATA_READ) {
        buff = (unsigned int *)data->b.dest;
        i = 0;
        while (i < (byte_cnt>>2)) {//word
            if(reg->status & BuffRDEn) {
                fifo_level = reg->buff_lvl & 0x1ff;
                while (fifo_level--) {
                    buff[i++] = *(volatile u32*)mmchost->database;
                    timeout = 0xffffff;
                }
            }
            if (!timeout--) {
                goto out;
            }
        }
    } else {
        buff = (unsigned int *)data->b.src;
        i = 0;
        while (i < (byte_cnt>>2)) {//word
            if (reg->status & BuffWREn) {
                fifo_level_leftover = FIFO_DEPTH_WORD - (reg->buff_lvl & 0x1ff);
                while (fifo_level_leftover--) {
                    *(volatile u32*)mmchost->database = buff[i++];
                    timeout = 0xffffff;
                }
            }
            if (!timeout--) {
                goto out;
            }
        }
    }

out:
    /* use dma to access fifo */
    reg->ctrl3 &= (~CPUAcessBuffEn);

    if (timeout <= 0){
        mmcinfo("mmc %d transfer by cpu failed\n",mmchost ->mmc_no);
        return -1;
    }

    return 0;
}

static int mmc_trans_data_by_dma(struct mmc *mmc, struct mmc_data *data)
{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v5p1 *reg = (struct mmc_reg_v5p1 *)mmchost->reg;
    struct mmc_des_v5p1 *pdes = NULL;
    unsigned byte_cnt = data->blocksize * data->blocks;
    unsigned char *buff;
    unsigned des_idx = 0;
    unsigned buff_frag_num = 0;
    unsigned remain;
    unsigned i, rval;
    int ret = 0;

    buff = data->flags & MMC_DATA_READ ?
            (unsigned char *)data->b.dest : (unsigned char *)data->b.src;
    buff_frag_num = byte_cnt >> SMHC_V5P1_DES_NUM_SHIFT;
    remain = byte_cnt & (SMHC_V5P1_DES_BUFFER_MAX_LEN-1);

    if (remain)
        buff_frag_num ++;
    else
        remain = SMHC_V5P1_DES_BUFFER_MAX_LEN;

    pdes = mmchost->pdes;

    for (i=0; i < buff_frag_num; i++, des_idx++) {
        memset((void*)&pdes[des_idx], 0, sizeof(struct mmc_des_v5p1));
        pdes[des_idx].valid = 1;
        pdes[des_idx].act = ACT_TRANS;

        if (buff_frag_num > 1 && i != buff_frag_num-1) {
            pdes[des_idx].length = SMHC_V5P1_DES_BUFFER_MAX_LEN;
        } else
            pdes[des_idx].length = remain;

        pdes[des_idx].addr = (u32)buff + i * SMHC_V5P1_DES_BUFFER_MAX_LEN;

        if (i == buff_frag_num-1) {
            pdes[des_idx].end = 1;
            pdes[des_idx].int_en = 1;
        }
        mmcdbg("mmc %d frag %d, remain %d, des[%d](%x): "
            "[0] = %x, [1] = %x\n",mmchost ->mmc_no,
            i, remain, des_idx, (u32)&pdes[des_idx],
            (u32)((u32*)&pdes[des_idx])[0], (u32)((u32*)&pdes[des_idx])[1]);
    }

    /* use dma to access fifo */
    reg->ctrl3 &= (~CPUAcessBuffEn);

    /* select dma type */
    rval = reg->ctrl1;
    rval &= ~(0x3U<<3);
    rval |= 0x1U<<3;
    //rval |= 0x3U<<3;
    reg->ctrl1 = rval;

    /* set dma desc list base addres */
    reg->adma_addr = (u32)mmchost->pdes;

    return ret;
}

static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
            struct mmc_data *data)

{
    struct sunxi_mmc_host* mmchost = (struct sunxi_mmc_host *)mmc->priv;
    struct mmc_reg_v5p1 *reg = (struct mmc_reg_v5p1 *)mmchost->reg;
    unsigned int timeout = 0;
    int error = 0;
    unsigned int status = 0;
    unsigned int usedma = 0;
    unsigned int bytecnt = 0;
    u32 cmd_attr0 = ((cmd->cmdidx&0x3f) << 24);
    u32 cmd_attr1 = 0;

    if (mmchost->fatal_err) {
        mmcinfo("mmc %d Found fatal err,so no send cmd\n", mmchost ->mmc_no);
        return -1;
    }
    if (cmd->resp_type & MMC_RSP_BUSY)
        mmcdbg("mmc %d cmd %d check rsp busy\n", mmchost->mmc_no, cmd->cmdidx);

    if (cmd->cmdidx == 12)
        return 0;

#ifdef MMC_TRANS_BY_DMA
    /* dma's descriptors are allocate at dram(0x42000000). smhc's dma only support descriptors and
    data buffer at same memory type(dram or sram) */
    if(data && ((u32)(data->b.dest) > 0x40000000))
        usedma = 1;
#endif

    /* wait command line idle */
    timeout = 0xff;
    while ((--timeout>0) && (reg->status&CmdInhibitCmd)) ;
    if (timeout == 0) {
        mmcinfo("wait Command Inhibit(CMD) timeout!\n");
        error = -1;
        goto OUT;
    }

    if (!cmd->cmdidx)
        cmd_attr1 |= SendInitSeq;

    if (cmd->resp_type & MMC_RSP_PRESENT)
    {
        if (cmd->resp_type & MMC_RSP_136)
            cmd_attr0 |= Rsp136;
        else if (cmd->resp_type & MMC_RSP_BUSY)
            cmd_attr0 |= Rsp48b;
        else
            cmd_attr0 |= Rsp48;

        if (cmd->resp_type & MMC_RSP_CRC)
            cmd_attr0 |= CheckRspCRC; /* response crc */
        if (cmd->resp_type & MMC_RSP_OPCODE)
            cmd_attr0 |= CheckRspIdx; /* response cmd index */
    }

    if (data)
    {
        /* wait data line idle */
        timeout = 0xff;
        while ((--timeout>0) && (reg->status&CmdInhibitDat)) ;
        if (timeout == 0) {
            mmcinfo("wait Command Inhibit(DAT) timeout!\n");
            error = -1;
            goto OUT;
        }

        if ((u32)data->b.dest & 0x3) {
            mmcinfo("mmc %d dest is not 4 byte align\n",mmchost ->mmc_no);
            error = -1;
            goto OUT;
        }

        cmd_attr0 |= DataExp;

        if (data->flags & MMC_DATA_READ)
            cmd_attr0 |= Read;

        if (usedma)
            cmd_attr0 |= DMAEn;

        if (data->blocks > 1) {
            cmd_attr0 |= MultiBlkTrans;

            /* make sure auto cmd12 arg is 0 to avoid to be considered as HPI cmd by some emmc that support HPI  */
            cmd_attr0 |= AutoCmd12;
            reg->cmd_arg2 = 0x0;
        }

        reg->blk_cfg = ((data->blocks&0xffff)<<16) | (data->blocksize&0xfff);
    }

    reg->int_sta = 0xffffffff; /* clear int status */
    reg->cmd_arg1= cmd->cmdarg;
    reg->cmd_attr = cmd_attr1;

    mmcdbg("mmc %d, cmd %d(0x%x), arg 0x%x\n", mmchost->mmc_no, cmd->cmdidx,
        cmd_attr0, cmd->cmdarg);

    /* send cmd */
    if (!data)
    {
        reg->cmd = cmd_attr0;
    }
    else
    {
        int ret = 0;
        bytecnt = data->blocksize * data->blocks;
        mmcdbg("mmc %d trans data %d bytes\n",mmchost->mmc_no, bytecnt);
        if (usedma)
        {
            ret = mmc_trans_data_by_dma(mmc, data);
            reg->cmd = cmd_attr0;
        }
        else
        {
            reg->cmd = cmd_attr0;
            ret = mmc_trans_data_by_cpu(mmc, data);
        }

        if (ret) {
            mmcinfo("mmc %d Transfer failed\n", mmchost->mmc_no);
            error = reg->int_sta & 0x437f0000;
            if(!error)
                error = 0xffffffff;
            goto OUT;
        }
    }

    timeout = 0xffffff;
    do {
        status = reg->int_sta;
        if (!timeout-- || (status & 0x437f0000)) {
            error = status & 0x437f0000;
            if (!error)
                error = 0xffffffff; //represet software timeout
            mmcinfo("mmc %d cmd %d timeout, err %x\n",mmchost->mmc_no, cmd->cmdidx, error);
            goto OUT;
        }
    } while (!(status&CmdOverInt));

    if (data) {
        timeout = usedma ? 0xffff*bytecnt : 0xffff;

        do {
            status = reg->int_sta;
            if (!timeout-- || (status & 0x437f0000)) {
                error = status & 0x437f0000;
                if(!error)
                    error = 0xffffffff;//represet software timeout
                mmcinfo("mmc %d data timeout, err %x\n",mmchost->mmc_no, error);
                goto OUT;
            }
        } while (!(status&TransOverInt));

        if (usedma) {
            timeout = 0xffffff;
            mmcdbg("mmc %d cacl rd dma timeout %x\n", mmchost->mmc_no, timeout);
            do {
                status = reg->int_sta;
                if (!timeout-- || (status & 0x437f0000)) {
                    error = status & 0x437f0000;
                    if(!error)
                        error = 0xffffffff; //represet software timeout
                    mmcinfo("mmc %d wait dma over err %x, adma err %x, current des 0x%x 0x%x\n",
                        mmchost->mmc_no, error, reg->adma_err, reg->cddlw, reg->cddhw);
                    goto OUT;
                }
            } while (!(status&DmaInt));
        }
    }

    if (cmd->resp_type & MMC_RSP_BUSY) {
        timeout = 0x4ffffff;
        do {
            status = reg->status;
            if (!timeout--) {
                error = -1;
                mmcinfo("mmc %d busy timeout\n", mmchost->mmc_no);
                goto OUT;
            }
        } while (!(status&Dat0LineSta));
    }

    if (cmd->resp_type & MMC_RSP_136) {
        /* Response Field[127:8] <-> RESP[119:0] */
        cmd->response[0] = ((reg->resp3 & 0xffffff) << 8);
        cmd->response[0] |= ((reg->resp2 >>24) & 0xff);
        cmd->response[1] = ((reg->resp2 & 0xffffff) << 8);
        cmd->response[1] |= ((reg->resp1 >>24) & 0xff);
        cmd->response[2] = ((reg->resp1 & 0xffffff) << 8);
        cmd->response[2] |= ((reg->resp0 >>24) & 0xff);
        cmd->response[3] = ((reg->resp0 & 0xffffff) << 8);

        mmcdbg("mmc %d resp 0x%x 0x%x 0x%x 0x%x\n", mmchost->mmc_no,
            cmd->response[3], cmd->response[2],
            cmd->response[1], cmd->response[0]);
    } else {
        cmd->response[0] = reg->resp0;
        mmcdbg("mmc %d resp 0x%x\n", mmchost->mmc_no, cmd->response[0]);
    }


OUT:

    if (error) {
        mmc_v5p1_save_regs(mmc, &reg_v5p1_bak);
        mmc_v5p1_module_reset(mmc);
        mmc_v5p1_restore_regs(mmc, &reg_v5p1_bak);
        mmcinfo("mmc %d cmd %d err %x\n",mmchost->mmc_no, cmd->cmdidx, error);
    }

    reg->int_sta = 0xffffffff; /*clear all interrupt*/
    reg->int_sta_en = 0xffffffff;

    if (error)
        return -1;
    else
        return 0;
}

int mmc_v5p1_init(int sdc_no, unsigned bus_width, const normal_gpio_cfg *gpio_info, int offset ,void *extra_data)
{
    struct mmc *mmc;
    int ret;

    mmcinfo("mmc driver ver %s\n", DRIVER_VER);

    memset(&mmc_dev[sdc_no], 0, sizeof(struct mmc));
    memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host));
    mmc = &mmc_dev[sdc_no];
    mmc_host[sdc_no].mmc = mmc;

    if ((sdc_no == 0) || (sdc_no == 1))
        mmc_host[sdc_no].timing_mode = SUNXI_MMC_TIMING_MODE_1; //SUNXI_MMC_TIMING_MODE_3
    else if ((sdc_no == 2) || (sdc_no == 3))
        mmc_host[sdc_no].timing_mode = SUNXI_MMC_TIMING_MODE_4;

    strcpy(mmc->name, "SUNXI SD/MMC");
    mmc->priv = &mmc_host[sdc_no];
    mmc->send_cmd = mmc_send_cmd;
    mmc->set_ios = mmc_set_ios;
    mmc->init = mmc_core_init;
    mmc->update_phase = NULL;

    mmc->voltages = MMC_VDD_29_30|MMC_VDD_30_31|MMC_VDD_31_32|MMC_VDD_32_33|
                    MMC_VDD_33_34|MMC_VDD_34_35|MMC_VDD_35_36;
    mmc->host_caps = MMC_MODE_HS_52MHz|MMC_MODE_HS|MMC_MODE_HC;
    if (bus_width == 4)
        mmc->host_caps |= MMC_MODE_4BIT;
    if (((sdc_no == 2) || (sdc_no == 3)) && (bus_width == 8)) {
        mmc->host_caps |= MMC_MODE_8BIT | MMC_MODE_4BIT;
    }

    mmc->f_min = 400000;
    mmc->f_max = 50000000;
    mmc->f_max_ddr = 50000000;
    mmc->control_num = sdc_no;

    mmc_host[sdc_no].pdes = (struct mmc_des_v5p1*)DMAC_DES_BASE_IN_SDRAM;
    if (mmc_resource_init(sdc_no)){
        mmcinfo("mmc %d resource init failed\n",sdc_no);
        return -1;
    }

    mmc_clk_io_onoff(sdc_no, 1, gpio_info, offset);
    ret = mmc_register(sdc_no, mmc);
    if (ret < 0){
        mmcinfo("mmc %d register failed\n",sdc_no);
        return -1;
    }

    return mmc->lba;
}

int mmc_v5p1_exit(int sdc_no, const normal_gpio_cfg *gpio_info, int offset)
{
    mmc_clk_io_onoff(sdc_no, 0, gpio_info, offset);
    mmc_unregister(sdc_no);
    memset(&mmc_dev[sdc_no], 0, sizeof(struct mmc));
    memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host));

    mmcdbg("sunxi mmc%d exit\n",sdc_no);
    return 0;
}


